package org.xtetris;

import javax.microedition.lcdui.Graphics;

import org.util.Util;

public class Game implements Constants , MenuListener{
	private int leftMargin = 5;
	private int rightMargin = 5;
	private int topMargin = 5;
	private int bottomMargin = 5;
	
	private final int brickSize = 16;
	
	private Map map;
	
	private final int numRows, numCols;
	private final int mapWidth ,mapHeight;
	
	private final static int BRICKS_OF_INFO_AREA = 4;
	
	private final int infoAreaWidth = brickSize * BRICKS_OF_INFO_AREA;
	private final int infoAreaHeight;
	
	Shape currentShape , nextShape;
	Shape shadow;
	
	int tickSPerMove = 15;
	
	int tc;
	
	private long counter;
	
	private Menu menu;
	
	final static int MAX_SHAKE_COUNTER = 4;
	final static int[][] SHAKE_AMPLITUDE = 
	{
		{-1 , -2},
		{ 2 ,  1},
		{-2 ,  1},
		{ 1 , -2}
	};
	
	final static int SHAKE_AMPLITUDE_X = 0;
	final static int SHAKE_AMPLITUDE_Y = 1;
	
	int shakeAmplitudeIndex = 0;
	int shakeCounter;
	
	private final static int STATE_INITIALIZE = 0;
	private final static int STATE_SHAPE_DROP_DOWN = 1;
	private final static int STATE_SHAPE_ACCELERATE_DROP_DOWN = 2;
	private final static int STATE_SHOW_FULL_ROWS = 3;
	private final static int STATE_FAIL_TO_PLACE_SHAPE = 4;
	private final static int STATE_MENU = 5;
	
	int previousState;
	int state;
	int nextState;
	
	private final static int NUM_FADE_IN_STEPS = 10;
	
	private int sourceColor = COLOR_BLACK;
	private int targetColor = COLOR_WHITE;
	
	private final int sourceRed = (sourceColor & 0xff0000) >>> 16;
	private final int sourceGreen = (sourceColor & 0xff00) >>> 8;
	private final int sourceBlue = (sourceColor & 0xff);
	
	private final int targetRed = (targetColor & 0xff0000) >>> 16;
	private final int targetGreen = (targetColor & 0xff00) >>> 8;
	private final int targetBlue = (targetColor & 0xff);
	
	private final int redStep , greenStep , blueStep;
	
	private int backGroundColor = COLOR_BLACK;
	
	private final int ACCELERATION_PER_TICK = 6;
	
	private final int TICKS_OF_SHOW_FULL_ROWS = 3;
	int showFullRowCounter;
	
	private Engine engine;
	public Game(Engine engine){
		this.engine = engine;
		
		numRows = (engine.fullScreenHeight - topMargin - bottomMargin) / brickSize;
		numCols = (engine.fullScreenWidth - infoAreaWidth - leftMargin - rightMargin) / brickSize;
		
		mapWidth = numCols * brickSize;
		mapHeight = numRows * brickSize;
		
		infoAreaHeight = mapHeight;
		
		final int hw = (engine.fullScreenWidth - mapWidth) >> 1;
//		leftMargin = hw;
//		rightMargin = fullScreenWidth - leftMargin - mapWidth;
		
		final int hh = (engine.fullScreenHeight - mapHeight) >> 1;
//		topMargin = hh;
//		bottomMargin = fullScreenHeight - mapHeight - topMargin;
		
		map = new Map(numRows , numCols);
		
		redStep = (targetRed - sourceRed) / NUM_FADE_IN_STEPS;
		greenStep = (targetGreen - sourceGreen) / NUM_FADE_IN_STEPS;
		blueStep = (targetBlue - sourceBlue) / NUM_FADE_IN_STEPS;
	}
	
	Shape getRandomShape(){
		int si = Util.r.nextInt(Shape.getNumberOfShapes());
		int[][][] shape = Shape.SHAPES[si];
		int[][] state = shape[0];
		
		int brickWidth = state[0].length;
		int brickHeight = state.length;
		
		Shape s = new Shape(shape , Util.getRandomColor() , (numCols - brickWidth) >> 1 , 0/*(numRows - brickHeight) >> 1*/);
		
		return s;
	}
	
	boolean canPlaceShape(Shape shape){
		int[][] data = shape.shape[shape.state];
		
		boolean hitBottom = shape.y + data.length - 1 >= numRows;
		
		if(hitBottom){
			return false;
		}
		
		while(true/*shape.y + data.length - 1 >= 0*/){
			boolean foundCollision = false;
			
			rowdetection:for (int i = data.length - 1; i >= 0; i--) {
				final int row = shape.y + i;				
				
				for (int j = data[i].length - 1; j >= 0; j--) {
					final int col = shape.x + j;
					
					boolean hitBrickInMap = data[i][j] != Shape.EMPTY && map.getDataAt(row , col) != Map.EMPTY;
					
					if(hitBrickInMap){
						foundCollision = true;
						break rowdetection;
					}
				}
			}
			
			if(foundCollision){
				shape.y --;
				
			}else{
				if(shape.y + data.length - 1 >= 0){
					return true;
				}else{
					break;
				}
			}
		}
		
		return false;
	}
	
	
	boolean canMoveDown(Shape shape){
		int[][] data = shape.shape[shape.state];
		
		int x = shape.x;
		int y = shape.y + 1;
		
		boolean hitBottom = y + data.length - 1 >= numRows;
		
		if(hitBottom){
			return false;
		}
		
		for (int i = data.length - 1; i >= 0; i--) {
			for (int j = data[i].length - 1; j >= 0; j--) {
				final int row = y + i;
				final int col = x + j;
//				if(row < 0){
//					return true;
//				}
				
				boolean hitBrickInMap = data[i][j] != Shape.EMPTY && map.getDataAt(row , col) != Map.EMPTY;
				
				if(hitBrickInMap){
					return false;
				}
			}
		}
		
		return true;
	}
	
	boolean canMoveLeft(){
		int[][] data = currentShape.shape[currentShape.state];
		
		int x = currentShape.x - 1;
		int y = currentShape.y;
		
		boolean hitLeft = x < 0;
		
		if(hitLeft){
			return false;
		}
		
		for(int i = 0; i < data[0].length;i ++){
			for (int j = data.length - 1; j >= 0; j--) {
				boolean hitBrickInMap = data[j][i] != Shape.EMPTY && map.getDataAt(y + j, x + i) != Map.EMPTY;
				
				if(hitBrickInMap){
					return false;
				}
			}
		}
		
		return true;
	}
	
	boolean canMoveRight(){
		int[][] data = currentShape.shape[currentShape.state];
		
		int x = currentShape.x + 1;
		int y = currentShape.y;
		
		boolean hitRight = x + data[0].length - 1 >= numCols;
		
		if(hitRight){
			return false;
		}
		
		for (int i = data[0].length - 1; i >= 0; i --) {
			for (int j = data.length - 1; j >= 0; j --) {
				boolean hitBrickInMap = data[j][i] != Shape.EMPTY && map.getDataAt(y + j, x + i) != Map.EMPTY;
				if(hitBrickInMap){
					return false;
				}
			}
		}
		
		return true;
	}
	
	boolean canRotate(){
		int[][] data = currentShape.shape[currentShape.nextState()];
		
		int x = currentShape.x;
		int y = currentShape.y;
		
		for (int i = data.length - 1; i >= 0; i --) {
			for (int j = data[i].length - 1; j >= 0; j --) {
				boolean hitMap = x < 0 || x + j >= numCols || y + i>= numRows;
				
				if(hitMap){
					return false;
				}
				
				boolean hitBrickInMap = data[i][j] != Shape.EMPTY && map.getDataAt(y + i, x + j) != Map.EMPTY;
				if(hitBrickInMap){
					return false;
				}
			}
		}
		
		return true;
	}
	
	private void adjustShadow(){
		shadow.x = currentShape.x;
		shadow.state = currentShape.state;
		shadow.y = 0;
		while(canMoveDown(shadow)){
			shadow.y ++;
		}
	}
	
	void tick() {
		counter ++;
		
		tc = (tc + 1) % tickSPerMove;
		
		if(state == STATE_INITIALIZE){
			if(backGroundColor == targetColor){
				nextState = STATE_SHAPE_DROP_DOWN;
				setState();
			}else{
				int r = (backGroundColor & 0xff0000) >>> 16;
				int g = (backGroundColor & 0xff00) >>> 8;
				int b = (backGroundColor & 0xff);
				
				if(r != targetRed){
					r = (r + redStep) % 256;					
				}
				
				if(g != targetGreen){
					g = (r + greenStep) % 256;					
				}
				
				if(b != targetBlue){
					b = (b + blueStep) % 256;					
				}
				
				if(Math.abs(r - targetRed) < redStep){
					r = targetRed;
				}
				
				if(Math.abs(g - targetGreen) < greenStep){
					g = targetGreen;
				}
				
				if(Math.abs(b - targetBlue) < blueStep){
					b = targetBlue;
				}
				
				backGroundColor = (r << 16) | (g << 8) | b;
			}
		}else if(state == STATE_SHAPE_DROP_DOWN){
			if(tc == 0){
				if(canMoveDown(currentShape)){
					currentShape.y ++;	
				}else{
					updateMapWithShape();
				}				
			}
			
			if (engine.isKeyPressed(KEY_LEFT)) {
				if(canMoveLeft()){
					currentShape.x --;
					adjustShadow();
				}
			}
			
			if (engine.isKeyPressed(KEY_RIGHT)) {
				if(canMoveRight()){
					currentShape.x ++;
					adjustShadow();
				}
			}

			if (engine.isKeyPressed(KEY_UP)) {
				if(canRotate()){
					currentShape.rotate();
					adjustShadow();
				}
			}
			
			if (engine.isKeyPressed(KEY_DOWN)) {
				nextState = STATE_SHAPE_ACCELERATE_DROP_DOWN;
				setState();
			}
		}else if(state == STATE_SHAPE_ACCELERATE_DROP_DOWN){
			boolean canMoveDownAtLeastOnce = canMoveDown(currentShape);
			
			if(canMoveDownAtLeastOnce){
				for (int i = ACCELERATION_PER_TICK - 1; i >= 0; i--) {
					if(canMoveDown(currentShape)){
						currentShape.y ++;
					}else{
						//hit the ground
						break;
					}
				}				
			}else{
				updateMapWithShape();
			}
		}else if(state == STATE_SHOW_FULL_ROWS){
			if(showFullRowCounter < 0){
				//remove the full rows
				nextState = STATE_SHAPE_DROP_DOWN;
				setState();
			}
			
			showFullRowCounter --;
		}else if(state == STATE_FAIL_TO_PLACE_SHAPE){
			if(engine.isKeyPressed(KEY_FIRE)){
				nextState = STATE_INITIALIZE;
				setState();
			}
		}else if(state == STATE_MENU){
			menu.tick();
		}

		if(engine.isKeyPressed(KEY_RIGHT_SOFT)){
			nextState = STATE_MENU;
			setState();
		}
		
//		if (engine.isKeyPressed(KEY_FIRE)) {
//		}
//
//		if (engine.isKeyPressed(KEY_POUND)) {
//		}
//		
//		if (engine.isKeyPressed(KEY_STAR)) {
//		}
//		
//		if (engine.isKeyPressed(KEY_0)) {
//			
//		}
	}
	
	void updateMapWithShape(){
		int[][] data = currentShape.shape[currentShape.state];
		
		int x = currentShape.x;
		int y = currentShape.y;
		
		for (int i = data.length - 1; i >= 0; i --) {
			final int row = y + i;
			
			if(row < 0){
				state = STATE_FAIL_TO_PLACE_SHAPE;
				break;
			}
			for (int j = data[i].length - 1; j >= 0; j --) {
				final int col = x + j;
				
				int brick = data[i][j];
				if(brick != Shape.EMPTY){
					if(map.getDataAt(row, col) != Map.EMPTY){
						throw new IllegalStateException("map override found map[" + row + "][" + col + "] = " + map.getDataAt(row, col));
					}
					
					map.setDataAt(row, col, currentShape.color);
				}
			}
		}
		
		if(state == STATE_FAIL_TO_PLACE_SHAPE){
			
		}else{
			int numFullRows = 0;
			for (int i = 0; i < map.flag.length; i++) {
				if(map.flag[i] == Map.FULL_ROW){
					numFullRows ++;
				}
			}
			
			if(numFullRows > 0){
				showFullRowCounter = TICKS_OF_SHOW_FULL_ROWS;
				state = STATE_SHOW_FULL_ROWS;
			}else{
				currentShape = nextShape;
				nextShape = getRandomShape();
				
				shadow = new Shape(currentShape);
				adjustShadow();
				
				if(canPlaceShape(currentShape)){
					if(state == STATE_SHAPE_ACCELERATE_DROP_DOWN){
						shakeCounter = MAX_SHAKE_COUNTER;					
					}
					state = STATE_SHAPE_DROP_DOWN;
				}else{
					state = STATE_FAIL_TO_PLACE_SHAPE;
				}
			}
		}
	}
	
	private void paintShape(Graphics g , Shape shape , int x ,int y){
		final int tx = x;
		
		int[][] sd = shape.shape[shape.state];
		for (int i = 0; i < sd.length; i++) {
			for (int j = 0; j < sd[i].length; j++) {
				if(sd[i][j] != Shape.EMPTY){
					g.setColor(shape.color);
					Util.fillRect(g, x, y, brickSize, brickSize);
					
					g.setColor(COLOR_DARK_GRAY);					
					Util.drawRect(g, x, y, brickSize, brickSize);
				}
				x += brickSize;
			}
			
			x = tx;
			y += brickSize;
		}
	}
	
	private void paintMap(Graphics g , int x , int y){
		int tx = x, ty = y;
		
		for (int i = 0; i < numRows; i ++) {
			if(state == STATE_SHOW_FULL_ROWS){
				if(map.flag[i] == Map.FULL_ROW){
					if(showFullRowCounter % 2 == 0){
						tx = x;
						ty += brickSize;
						continue;
					}							
				}
			}
			
			for (int j = 0; j < numCols; j ++) {
				final int data = map.getDataAt(i, j);
				
				if(data == Map.EMPTY){
					g.setColor(backGroundColor);
				}else{
					g.setColor(data);
				}
				
				Util.fillRect(g , tx , ty , brickSize, brickSize);
				tx += brickSize;
			}
			
			tx = x;
			ty += brickSize;
		}
		
		//paint lines of rows and cols
		tx = x;
		ty = y;
		for (int i = 0; i <= numRows; i++) {
			g.setColor(COLOR_DARK_GRAY);
			Util.fillRect(g, tx, ty, mapWidth, 1);
			ty += brickSize;
		}
		
		tx = x;
		ty = y;
		for (int i = 0; i <= numCols; i++) {
			g.setColor(COLOR_DARK_GRAY);
			Util.fillRect(g, tx, ty, 1, mapHeight);
			tx += brickSize;
		}
	}
	
	/**
	 * make the state transformation from state to nextState 
	 */
	private void setState(){
		if(state == STATE_INITIALIZE){
			if(nextState == STATE_SHAPE_DROP_DOWN){
				currentShape = getRandomShape();
				nextShape = getRandomShape();
				
				shadow = new Shape(currentShape);
				adjustShadow();
				
				map.clear();
				
				state = STATE_SHAPE_DROP_DOWN;
			}
		}else if(state == STATE_FAIL_TO_PLACE_SHAPE){
			if(nextState == STATE_INITIALIZE){
				map.clear();
				nextShape = null;
				backGroundColor = sourceColor;
				
				state = STATE_INITIALIZE;
			}
		}else if(state == STATE_SHAPE_DROP_DOWN){
			if(nextState == STATE_SHAPE_ACCELERATE_DROP_DOWN){
				state = STATE_SHAPE_ACCELERATE_DROP_DOWN;
			}
		}else if(state == STATE_SHOW_FULL_ROWS){
			if(nextState == STATE_SHAPE_DROP_DOWN){
				map.pack();
				
				currentShape = nextShape;
				nextShape = getRandomShape();
				
				shadow = new Shape(currentShape);
				adjustShadow();
				
				state = STATE_SHAPE_DROP_DOWN;
			}
		}
		
		if(nextState == STATE_MENU){
			menu = new Menu(engine , Menu.MENU_TYPE_IN_GAME , null, (engine.fullScreenWidth - Menu.getWidth(Menu.MENU_TYPE_IN_GAME)) >> 1 , engine.fullScreenHeight / 3 + ((engine.fullScreenHeight) * 2 / 3 - Menu.getHeight(Menu.MENU_TYPE_IN_GAME)) >> 1, this);
			state = STATE_MENU;
		}
		
		previousState = state;
	}
	
	protected void paint(Graphics g) {
		int x = leftMargin;
		int y = topMargin;
		
		g.setColor(COLOR_WHITE);
		g.fillRect(0, 0, engine.fullScreenWidth, engine.fullScreenHeight);
		
		if(shakeCounter == 0){
			
		}else{
			x += SHAKE_AMPLITUDE[shakeAmplitudeIndex][SHAKE_AMPLITUDE_X];
			y += SHAKE_AMPLITUDE[shakeAmplitudeIndex][SHAKE_AMPLITUDE_Y];
			
			shakeAmplitudeIndex = (shakeAmplitudeIndex + 1) % SHAKE_AMPLITUDE.length;
			
			shakeCounter -- ;
		}
		
		//paint the map and the bricks in it
		int tx = x;
		int ty = y;
		
		paintMap(g, x, y);
		
		if(state == STATE_INITIALIZE){
			
		}else if(state == STATE_SHAPE_DROP_DOWN){
			final int bx = x + brickSize * currentShape.x;
			final int by = y + brickSize * currentShape.y;
			
			final int sx = x + brickSize * shadow.x;
			final int sy = y + brickSize * shadow.y;
			
			//paint the current brick
			paintShape(g, currentShape, bx, by);
			
//			if((shadow.y - currentShape.y) >= numRows / 3){
//				shadow.color = Util.getRandomColor();
//				paintShape(g, shadow,sx , sy);				
//			}
			
			shadow.color = Util.getRandomColor();
			paintShape(g, shadow,sx , sy);
		}else if(state == STATE_SHAPE_ACCELERATE_DROP_DOWN){
			final int bx = x + brickSize * currentShape.x;
			final int by = y + brickSize * currentShape.y;
			
			final int sx = x + brickSize * shadow.x;
			final int sy = y + brickSize * shadow.y;
			
			paintShape(g, currentShape, bx, by);
		}else if(state == STATE_SHOW_FULL_ROWS){
			
		}else if(state == STATE_FAIL_TO_PLACE_SHAPE){
			final int bx = x + brickSize * currentShape.x;
			final int by = y + brickSize * currentShape.y;
			
			final int sx = x + brickSize * shadow.x;
			final int sy = y + brickSize * shadow.y;
			
			g.setColor(COLOR_BLACK);
			
			paintShape(g, currentShape, bx, by);
			Util.drawString(g , "Ooops ~~~ 5 to restart" , leftMargin , topMargin , Graphics.LEFT | Graphics.TOP);
		}else if(state == STATE_MENU){
			menu.paint(g);
		}
		
		if(nextShape != null){
			//paint the info area
			int[][] data = nextShape.shape[nextShape.state];
			
			final int offsetX = leftMargin + mapWidth + rightMargin + ((BRICKS_OF_INFO_AREA - data[0].length) >> 1) * brickSize;
			final int offsetY = topMargin;
			
			paintShape(g, nextShape, offsetX, offsetY);	
		}
	}

	public void itemSelected(Menu menu, int index) {
		// TODO Auto-generated method stub
		if(menu.id == Menu.MENU_TYPE_IN_GAME){
			if(index == 0){
				state = STATE_SHAPE_DROP_DOWN;
			}
		}
	}
}
